package cate.game.pvp;


import easy.java.dev.note.NoteField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.annotation.Transient;

import java.util.Date;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;

public class PvpSeasonCalculator<E extends AbstractPvpSeasonPeriod<E>> {
    private static final Logger logger = LoggerFactory.getLogger(PvpSeasonCalculator.class);
    @NoteField(value = "赛季")
    public int season;
    @NoteField(value = "阶段")
    public int period;

    @NoteField(value = "下一次检查的时间")
    @Transient
    public transient long checkTime;
    /** 为了以为可能存在的调整，下列属性不做存储，需要功能做初始化 */
    @NoteField(value = "赛季切换时触发的重置方法")
    @Transient
    public transient Consumer<Void> resetMethod;
    @NoteField(value = "自定义赛季方法")
    @Transient
    public transient Function<Integer, Boolean> customizeMethod;
    @NoteField(value = "赛季开始的时间")
    @Transient
    public transient long startTime;
    @NoteField(value = "整个赛季周期")
    @Transient
    private transient long wholeSeasonTime;
    @NoteField(value = "没有开放时的阶段，可以为空")
    @Transient
    private transient E notOpen;
    @NoteField(value = "所有赛季阶段")
    @Transient
    private transient List<E> periods;

    public PvpSeasonCalculator(){

    }

    /**
     * 不指定NOT_OPEN的话，未开启状态默认为periods.get(0);
     * @param startTime 功能开启时间点
     * @param wholeSeasonTime 整个赛季持续时间
     * @param periods 所有时期
     */
    public PvpSeasonCalculator(long startTime, long wholeSeasonTime, List<E> periods){
        this(startTime, wholeSeasonTime, null, periods, null);
    }

    public PvpSeasonCalculator(long startTime, long wholeSeasonTime, E notOpen, List<E> periods){
        this(startTime, wholeSeasonTime, notOpen, periods, null);
    }

    public PvpSeasonCalculator(long startTime, long wholeSeasonTime, E notOpen, List<E> periods, Consumer<Void> resetMethod){
        initialize(startTime, wholeSeasonTime, notOpen, periods, resetMethod, null);
    }

    public PvpSeasonCalculator(long startTime, long wholeSeasonTime, E notOpen, List<E> periods, Consumer<Void> resetMethod, Function<Integer,Boolean> customizeMethod){
        initialize(startTime, wholeSeasonTime, notOpen, periods, resetMethod, customizeMethod);
    }

    public void initialize(long startTime, long wholeSeasonTime, E notOpen, List<E> periods, Consumer<Void> resetMethod, Function<Integer,Boolean> customizeMethod){
        this.startTime = startTime;
        this.wholeSeasonTime = wholeSeasonTime;
        this.notOpen = notOpen;
        this.periods = periods;
        this.resetMethod = resetMethod;
        this.customizeMethod = customizeMethod;
        resetCheckTime();
    }

    private void resetCheckTime(){
        if(season == 0){
            this.checkTime = startTime;
        }else if(period != periods.get(periods.size() - 1).getPeriod()){
            this.checkTime = startTime + wholeSeasonTime * (season -1) + (period == 0 ? 0 : getNextPeriod(period).getStartTime());
        }else{
            this.checkTime = startTime + wholeSeasonTime * (season);
        }
    }

    public static void main(String[] args) {

    }

    // 计算当前是第几赛季
    public int calcCurrentSeason() {
        return calcCurrentSeason(startTime, System.currentTimeMillis());
    }

    /**
     * tick
     */
    public void tick(){
        if(System.currentTimeMillis() < checkTime){
            return;
        }
        int currentSeason = calcCurrentSeason();
        if (currentSeason == 0) {
            return;
        }
        boolean goOn = customizeMethod == null || customizeMethod.apply(currentSeason);
        if(!goOn){
            return;
        }
        //当赛季差距大于1时，就不追了，内网经常出现会有问题
        boolean firstEnter = season == 0 || Math.abs(currentSeason - season) > 1;
        // 赛季判断
        if (season != currentSeason) {
            season = currentSeason;
            if(resetMethod != null){
                resetMethod.accept(null);
            }
            logger.info("进入新赛季：S{}",currentSeason);
        }
        E currentPeriod = currentSeasonPeriod();
        if(firstEnter){
            period = currentPeriod.getPeriod();
            logger.info("赛季{}阶段",currentPeriod);
            currentPeriod.handle(currentPeriod);
            return;
        }
        if (period != currentPeriod.getPeriod()) {
            logger.info("马上进入赛季{}阶段", currentPeriod);
            E st = getCurrentPeriod(period);
            if(st == null){
                period = currentPeriod.getPeriod();
                return;
            }
            int times = 0;
            while (currentPeriod.getPeriod() != st.getPeriod()) {
                logger.info("当前为赛季{}阶段",st);
                st = getNextPeriod(st);
                period = st.getPeriod();
                st.handle(st);
                times ++;
                //如果10次还没有结束循环，说明状态有问题，强制改成当前状态,防止死循环
                if(times > 10){
                    period = currentPeriod.getPeriod();
                    break;
                }
            }
        }
        resetCheckTime();
        logger.info("下次检查时间： {}", new Date(checkTime));
    }

    // 计算当前是第几赛季
    public int calcCurrentSeason(long startTime, long timestamp) {
        if (timestamp < startTime) {
            return 0;
        }
        if(wholeSeasonTime == 0){
            return 0;
        }
        long between = timestamp - startTime;
        return (int) (between / wholeSeasonTime) + 1;
    }

    // 当前赛季的开启时间
    public long calcCurSeasonStartTime() {
        long now = System.currentTimeMillis();
        if (now < startTime) {
            return startTime;
        }
        if(wholeSeasonTime == 0){
            return startTime;
        }
        long between = now - startTime;
        return now - between % wholeSeasonTime;
    }

    public long currentSeasonContinueTime() {
        return System.currentTimeMillis() - calcCurSeasonStartTime();
    }

    public E currentSeasonPeriod() {
        long now = System.currentTimeMillis();
        if (now < startTime) {
            return notOpen == null ? periods.get(0) : notOpen;
        }
        return getCurrentPeriod(currentSeasonContinueTime());
    }

    public E getCurrentPeriod(long seasonContinueTime) {
        for (E status : periods) {
            if (seasonContinueTime >= status.getStartTime() && seasonContinueTime <= status.getEndTime()) {
                return status;
            }
        }
        return null;
    }

    public E getCurrentPeriod(int period){
        return periods.stream().filter(e -> e.getPeriod() == period).findAny().orElse(notOpen);
    }

    public E getCurrentPeriod(){
        return periods.stream().filter(e -> e.getPeriod() == period).findAny().orElse(notOpen);
    }

    public E getNextPeriod(int period){
        return getNextPeriod(getCurrentPeriod(period));
    }

    public E getNextPeriod(E period) {
        if (period == null) {
            return null;
        }
        if(isLastPeriod(period)){
            return periods.get(0);
        }
        return periods.stream().filter(e -> e.getPeriod() == period.getPeriod() + 1).findAny().orElse(null);
    }

    private boolean isLastPeriod(E e){
        return periods.get(periods.size() - 1).getPeriod() <= e.getPeriod();
    }

    public void handlePeriod(){
        E e = getCurrentPeriod(this.period);
        if(e != null){
            e.handle(e);
        }
    }
}
